﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Text;
using System.Xml;

namespace SpriteAnimator.Classes
{
    /// <summary>
    /// Définie un sprite avec ses états
    /// </summary>
    public class Sprite: IDisposable, ICloneable
    {
        #region Constructeurs et destructeur

        /// <summary>
        /// Constructeur de la classe
        /// </summary>
        public Sprite()
        {
            this.Texture = null;
            this.BackColor = Color.CornflowerBlue;
            this.Frames = new List<Rectangle>();
            this.Sequences = new List<SpriteSequence>();
        }

        /// <summary>
        /// Libère les ressources
        /// </summary>
        public void Dispose()
        {
            if ( this.Texture != null )
                this.Texture.Dispose();

            this.Frames.Clear();
            this.Sequences.Clear();
            this.Texture = null;
        }

        #endregion

        #region Fonctions publiques

        /// <summary>
        /// Charge la définition du sprite
        /// </summary>
        /// <param name="p_sPath">Fichier de chargement</param>
        public void Load(string p_sPath)
        {
            XmlDocument oXml = new XmlDocument();

            // *** Chargement nom ***

            this.Name = Path.GetFileNameWithoutExtension(p_sPath);

            // *** Chargement du XML ***

            oXml.Load(p_sPath);

            // *** Récupération des informations ***

            if ( oXml["definition"].Attributes["backcolor"] != null && !string.IsNullOrEmpty(oXml["definition"].Attributes["backcolor"].Value) )
                this.BackColor = Color.FromArgb(int.Parse(oXml["definition"].Attributes["backcolor"].Value));

            // *** Ajout des frames ***

            this.Frames.Clear();

            foreach ( XmlNode oNode in oXml["definition"]["frames"].ChildNodes )
                this.Frames.Add(new Rectangle(int.Parse(oNode.Attributes["x"].Value), int.Parse(oNode.Attributes["y"].Value), int.Parse(oNode.Attributes["width"].Value), int.Parse(oNode.Attributes["height"].Value)));

            // *** Ajout des séquences ***

            this.Sequences.Clear();

            foreach ( XmlNode oNode in oXml["definition"]["sequences"].ChildNodes )
            {
                SpriteSequence oSeq = new SpriteSequence(oNode.Attributes["name"].Value);

                if ( oNode.Attributes["type"].Value.Equals("range") )
                    oSeq.Type = SpriteAnimationType.Range;
                else
                    oSeq.Type = SpriteAnimationType.List;

                // *** Découpage des frames ***

                string[] sIndexes = oNode.Attributes["index"].Value.Split(',');

                foreach ( string sIndex in sIndexes )
                    oSeq.FramesIndexes.Add(int.Parse(sIndex));

                // *** Ajout dans la liste ***

                this.Sequences.Add(oSeq);
            }

            // *** Chargement texture ***

            using ( FileStream oStream = new FileStream(Path.GetDirectoryName(p_sPath) + Path.GetFileNameWithoutExtension(p_sPath) + ".png", FileMode.Open) )
            {
                this.Texture = Image.FromStream(oStream);
            }

            // *** Sauvegarde du chemin ***

            this.FilePath = p_sPath;
        }

        /// <summary>
        /// Enregistre le projet sur le disque
        /// </summary>
        /// <param name="p_sPath">Chemin ou stocker</param>
        /// <param name="p_sFileNoExt">Nom du fichier sans l'extension</param>
        public void Save(string p_sPath, string p_sFileNoExt)
        {
            StringBuilder oXml = new StringBuilder();

            // *** Vérification path ***

            if ( p_sPath[p_sPath.Length - 1] != '\\' )
                p_sPath += "\\";

            // *** Création du XML ***

            oXml.Append("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
            oXml.Append("<definition backcolor=\"" + this.BackColor.ToArgb() + "\">\n");

            // *** Dump des frames ***

            oXml.Append("\t<frames>\n");

            foreach ( Rectangle oRect in this.Frames )
                oXml.Append("\t\t<frame x=\"" + oRect.X.ToString() + "\" y=\"" + oRect.Y.ToString() + "\" width=\"" + oRect.Width.ToString() + "\" height=\"" + oRect.Height.ToString() + "\" />\n");

            oXml.Append("\t</frames>\n");

            // *** Dump des séquences ***

            oXml.Append("\t<sequences>\n");

            foreach ( SpriteSequence oSeq in this.Sequences )
            {
                if ( oSeq.Type == SpriteAnimationType.List )
                {
                    oXml.Append("\t\t<sequence name=\"" + oSeq.Name + "\" type=\"list\" index=\"");

                    for ( int i = 0; i < oSeq.FramesIndexes.Count; i++ )
                    {
                        if ( i > 0 )
                            oXml.Append(",");

                        oXml.Append(oSeq.FramesIndexes[i].ToString());
                    }

                    oXml.Append("\" />\n");
                }
                else if ( oSeq.Type == SpriteAnimationType.Range && oSeq.FramesIndexes.Count == 2 )
                {
                    oXml.Append("\t\t<sequence name=\"" + oSeq.Name + "\" type=\"range\" index=\"");
                    oXml.Append(oSeq.FramesIndexes[0].ToString() + "," + oSeq.FramesIndexes[1].ToString());
                    oXml.Append("\" />\n");
                }
            }

            oXml.Append("\t</sequences>\n");

            // *** Footer ***

            oXml.Append("</definition>");

            // *** Sauvegarde du fichier n2d ***

            File.WriteAllText(p_sPath + p_sFileNoExt + ".n2d", oXml.ToString(), Encoding.UTF8);

            // *** Sauvegarde de l'image ***

            this.Texture.Save(p_sPath + p_sFileNoExt + ".png", ImageFormat.Png);

            // *** Sauvegarde du chemin ***

            this.Name = p_sFileNoExt;
            this.FilePath = p_sPath + p_sFileNoExt + ".n2d";
        }

        #endregion

        #region Indexer

        /// <summary>
        /// Affecte ou retourne la couleur de fond
        /// </summary>
        public Color BackColor
        {
            get;
            set;
        }

        /// <summary>
        /// Liste des frames du sprite
        /// </summary>
        public List<Rectangle> Frames
        {
            get;
            private set;
        }

        /// <summary>
        /// Affecte ou retourne le nom du projet sprite
        /// </summary>
        public string Name
        {
            get;
            set;
        }

        /// <summary>
        /// Dernier chemin de sortie
        /// </summary>
        public string FilePath
        {
            get;
            private set;
        }

        /// <summary>
        /// Liste des séquences du sprite
        /// </summary>
        public List<SpriteSequence> Sequences
        {
            get;
            private set;
        }

        /// <summary>
        /// Affecte ou retourne la texture
        /// </summary>
        public Image Texture
        {
            get;
            set;
        }

        #endregion

        #region ICloneable Members

        /// <summary>
        /// Clone l'instance
        /// </summary>
        /// <returns>Clone généré</returns>
        object ICloneable.Clone()
        {
            return (this.Clone());
        }

        /// <summary>
        /// Clone l'instance
        /// </summary>
        /// <returns>Clone généré</returns>
        public virtual Sprite Clone()
        {
            Sprite oClone = this.MemberwiseClone() as Sprite;
            oClone.Texture = this.Texture.Clone() as Image;

            oClone.Sequences = new List<SpriteSequence>();

            foreach ( SpriteSequence oSeq in this.Sequences )
                oClone.Sequences.Add(oSeq.Clone());

            oClone.Frames = new List<Rectangle>();

            foreach ( Rectangle oRect in this.Frames )
                oClone.Frames.Add(new Rectangle(oRect.X, oRect.Y, oRect.Width, oRect.Height));

            return (oClone);
        }

        #endregion
    }
}
